package com.wmh.baseservice.robot.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wmh.baseservice.common.ex.ServiceException;
import com.wmh.baseservice.common.mybatisplus.structure.LambdaQueryWrapperX;
import com.wmh.baseservice.common.utils.LogUtil;
import com.wmh.baseservice.common.utils.web.UserUtil;
import com.wmh.baseservice.robot.entity.*;
import com.wmh.baseservice.robot.enums.RobotStatusEnums;
import com.wmh.baseservice.robot.enums.TaskStatus;
import com.wmh.baseservice.robot.mapper.RobotInfoMapper;
import com.wmh.baseservice.robot.pojo.dto.TaskStatementAddDTO;
import com.wmh.baseservice.robot.pojo.vo.PushTaskReqVO;
import com.wmh.baseservice.robot.pojo.vo.RegRobotReqVO;
import com.wmh.baseservice.robot.pojo.vo.RobotUpdateStatusReqVO;
import com.wmh.baseservice.robot.service.RobotInfoService;
import com.wmh.baseservice.robot.service.RobotTaskStatementService;
import com.wmh.baseservice.robot.service.RobotTaskStatementV2Service;
import com.wmh.baseservice.robot.service.RobotTaskTemplateService;
import com.wmh.baseservice.robot.tools.MyHttpUtil;
import com.wmh.baseservice.common.utils.SnowFlakeIdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author 20402
 * @description 针对表【robot_info(机器信息表)】的数据库操作Service实现
 * @createDate 2022-11-07 17:35:46
 */
@Service
@Slf4j
@Valid
public class RobotInfoServiceImpl extends ServiceImpl<RobotInfoMapper, RobotInfo>
        implements RobotInfoService {

    @Resource
    @Lazy
    private MyHttpUtil myHttpUtil;

    @Resource
    private RobotTaskTemplateService robotTaskTemplateService;

    @Resource
    private RobotTaskStatementService robotTaskStatementService;

    @Resource
    private RobotTaskStatementV2Service robotTaskStatementV2Service;

    @Resource
    private Map<String, RobotApiUri> robotApiMap;

    @Resource
    private HttpServletRequest request;

    @Resource
    private UserUtil userUtil;

    @Override
    public String reg(@Validated RegRobotReqVO reqVo) {
        RobotRes res = myHttpUtil.post(request.getRemoteAddr(), reqVo.getPort(),
                robotApiMap.get("确认服务器连接").getUri(), null, false);
        if (res == null || RobotRes.OK_CODE != res.getCode()) {
            throw new ServiceException("注册失败 : " + res);
        }
        RobotInfo robot = new RobotInfo()
                .setRobotNumber(reqVo.getRobotNumber())
                .setRemoteCode(reqVo.getRemoteCode()).setRemotePwd(reqVo.getRemotePwd())
                .setRobotIp(request.getRemoteAddr()).setFlaskPort(reqVo.getPort());
        if (!saveOrUpdate(robot.setStatusId(1))) {
            throw new ServiceException("注册失败,请重试或联系管理员");
        }
        update(new UpdateWrapper<RobotInfo>()
                .eq(RobotInfo.ROBOT_NUMBER, robot.getRobotNumber())
                .set(RobotInfo.TASK_ID, null)
                .set(RobotInfo.TASK_NAME, null)
                .set(RobotInfo.TASK_TEMPLATE_ID, null)
        );
        return robot.getRobotNumber().toString();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void stopTask(Long robotNumber) {
        RobotInfo robot = checkRobotExist(robotNumber);
        if (robot.getStatusId() != RobotStatusEnums.RUNNING.getId()) {
            throw new ServiceException("该机器没有任务在执行,请刷新页面");
        }
        RobotRes robotRes = myHttpUtil.post(robot.getRobotIp(), robot.getFlaskPort(),
                robotApiMap.get("结束任务").getUri(), null, true);
        if (RobotRes.isError(robotRes)) {
            throw new ServiceException("该设备暂访问失败,请连接设备调整或联系管理员");
        }
        updateStatus(new RobotUpdateStatusReqVO().setRobotNumber(robot.getRobotNumber()).setStatusId(RobotStatusEnums.LEAVE_UNUSED.getId()));
    }

    @Override
    public void pushTask(@Valid PushTaskReqVO pushTaskReqVo) {
        List<String> ids = JSON.parseArray(pushTaskReqVo.getRobotIds(), String.class);
        // 获取需要发布任务的机器人列表
        List<RobotInfo> list = list(new QueryWrapper<RobotInfo>().in(RobotInfo.ROBOT_NUMBER, ids));
        if (list.isEmpty()) {
            throw new ServiceException("发布任务失败, 没有搜索到接收机器");
        }
        // 获取任务模板
        RobotTaskTemplate template = robotTaskTemplateService.getById(pushTaskReqVo.getTemplateId());
        // 准备错误信息
        StringBuilder errMsg = new StringBuilder("发布失败的任务机器ip : ");
        int baseLen = errMsg.length();
        for (RobotInfo robot : list) {
            Long statementId = SnowFlakeIdUtil.generate();
            // 设置任务响应 以及报表id 等内容
            JSONObject res = createTaskReps(template, pushTaskReqVo, statementId);
            if (robot.getStatusId() != RobotStatusEnums.LEAVE_UNUSED.getId()) {
                errMsg.append(robot.getRobotIp()).append("[非闲置状态] ");
                continue;
            }
            RobotRes robotRes = myHttpUtil.post(robot.getRobotIp()
                    , robot.getFlaskPort(), robotApiMap.get("获取任务").getUri(), res, true);
            if (RobotRes.isError(robotRes)) {
                errMsg.append(robot.getRobotIp()).append("[该设备操作失败] ");
                setRobotToStatusDrops(robot.getRobotNumber());
            }
            /* 发起任务成功生成任务报表 */
            else if (robotRes.getCode() == RobotRes.OK_CODE) {
                robot.setStatusId(2);
                addTaskStatement(robot, template);
            }
        }
        if (errMsg.length() > baseLen) {
            throw new ServiceException(errMsg.toString());
        }
        if (!saveOrUpdateBatch(list)) {
            throw new ServiceException("发布任务失败,请重试或联系管理员");
        }
    }

    @Override
    public void autoPushTask(String uriName, JSONObject params) {
        List<RobotInfo> robotInfo = getRobotInfo(RobotStatusEnums.LEAVE_UNUSED.getId());
        if (CollUtil.isEmpty(robotInfo)) {
            throw new ServiceException("当前没有闲置的机器");
        }
        // 使用机器的机器信息
        RobotInfo useInfo = null;
        for (RobotInfo info : robotInfo) {
            RobotRes res = myHttpUtil.post(info, uriName, params);
            log.info("自动选择[闲置]机器发布任务响应 : {} 机器信息 : {}", res, info);
            if (!RobotRes.isError(res)) {
                info.setStatusId(RobotStatusEnums.RUNNING.getId());
                useInfo = info;
                break;
            }
        }
        if (Objects.isNull(useInfo)) {
            throw new ServiceException("所有闲置机器不可用[请联系管理员]");
        }
        // 更新状态
        updateById(useInfo);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateStatus(@Validated RobotUpdateStatusReqVO reqVO) {
        RobotInfo robot = checkRobotExist(reqVO.getRobotNumber());
        if (reqVO.getStatusId() != RobotStatusEnums.RUNNING.getId()) {
            robot.setTaskId(null).setTaskName(null);
        } else {
            robot.setTaskId(reqVO.getTaskId()).setTaskName(reqVO.getTaskName());
        }
        if (!update(new UpdateWrapper<RobotInfo>()
                .eq(RobotInfo.ROBOT_NUMBER, reqVO.getRobotNumber())
                .set(RobotInfo.STATUS_ID, reqVO.getStatusId())
                .set(RobotInfo.TASK_NAME, robot.getTaskName())
                .set(RobotInfo.TASK_ID, robot.getTaskId()))) {
            throw new ServiceException("更新状态失败,请重试或联系管理员");
        }
        if (Objects.nonNull(reqVO.getStatementId())) {
            robotTaskStatementService.updateById(new RobotTaskStatement()
                    .setId(reqVO.getStatementId()).setTaskStatus(TaskStatus.RUNNING.getValue()));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStatusV2(RobotUpdateStatusReqVO reqVO) {
        checkRobotExist(reqVO.getRobotNumber());
        update(new UpdateWrapper<RobotInfo>().lambda()
                .eq(RobotInfo::getRobotNumber, reqVO.getRobotNumber())
                .set(RobotInfo::getStatusId, reqVO.getStatusId())
                .set(RobotInfo::getTaskName, reqVO.getTaskName())
                .set(RobotInfo::getTaskId, reqVO.getTaskId())
        );
        if (reqVO.getStatementId() != null) {
            robotTaskStatementV2Service.updateById(new RobotTaskStatementV2()
                    .setId(reqVO.getStatementId()).setTaskStatus(TaskStatus.RUNNING.getValue()));
        }
    }

    @Override
    public String getRobotLog(Long robotNumber) {
        RobotInfo robot = checkRobotExist(robotNumber);
        RobotRes res = myHttpUtil.post(robot.getRobotIp(), robot.getFlaskPort(), robotApiMap.get("获取日志").getUri(), null, true);
        if (RobotRes.isError(res)) {
            throw new ServiceException("机器访问失败,请重试或联系管理员");
        }
        return res.getData().toString();
    }

    @Override
    public String getScreenshots(Long robotNumber) {
        RobotInfo robot = checkRobotExist(robotNumber);
        RobotRes res = myHttpUtil.post(robot, "获取截图", null, true);
        if (RobotRes.isError(res)) {
            throw new ServiceException(res.getMessage());
        }
        return res.getData().toString();
    }

    @Override
    public void logout(Long robotNumber) {
        RobotInfo robot = checkRobotExist(robotNumber);
        /* 从数据库中删除 */
        if (!removeById(robotNumber)) {
            throw new ServiceException("注销失败,请重试或联系管理员");
        }
        /* 发起请求结束机器运行 */
        RobotRes res = myHttpUtil.post(robot, "退出应用", null, false);
        if (RobotRes.isError(res)) {
            throw new ServiceException("机器信息被删除,但机器程序未正常退出");
        }
    }

    @Override
    public void setRobotToStatusDrops(Long robotNumber) {
        RobotInfo info = getOne(new QueryWrapper<RobotInfo>().eq(RobotInfo.ROBOT_NUMBER, robotNumber));
        if (info == null) {
            LogUtil.error("设置机器状态为掉线时, 失败 robotNumber :{}", robotNumber);
            return;
        }
        if (!update(new UpdateWrapper<RobotInfo>()
                .eq(RobotInfo.ROBOT_NUMBER, info.getRobotNumber())
                .set(RobotInfo.STATUS_ID, RobotStatusEnums.LOST_CONNECTION.getId())
                .set(RobotInfo.TASK_NAME, null)
                .set(RobotInfo.TASK_ID, null))) {
            LogUtil.error("设置机器状态为掉线时, 失败 robotNumber :{}", robotNumber);
        }
    }

    @Override
    public List<RobotInfo> getRobotInfo(Integer robotStatus) {
        return list(new LambdaQueryWrapperX<RobotInfo>()
                .eq(RobotInfo::getStatusId, robotStatus)
        );
    }

    /**
     * 创建响应任务模板 即 将任务参数等内容传递到任务设备
     *
     * @param template      任务模板
     * @param pushTaskReqVo 发起任务vo
     * @return 响应 JSON
     */
    private JSONObject createTaskReps(RobotTaskTemplate template, PushTaskReqVO pushTaskReqVo, Long statementId) {
        JSONObject res = new JSONObject();
        // 对任务参数快照追加报表id 以及执行环境
        List<String> snapshots = JSON.parseArray(template.getTaskConfigSnapshot(), String.class);
        snapshots = snapshots.stream().map(snapshot -> {
            JSONObject object = JSON.parseObject(snapshot);
            object.put("statementId", statementId);
            object.put("isTestExecute", pushTaskReqVo.getIsTestExecute());
            return JSON.toJSONString(object);
        }).collect(Collectors.toList());
        template.setTaskConfigSnapshot(JSON.toJSONString(snapshots));
        res.put("template", template);
        // 追加发起人 id
        res.put("adminId", userUtil.getId().toString());
        return res;
    }

    /**
     * 添加任务报表
     *
     * @param robot    机器信息
     * @param template 任务模板
     */
    private void addTaskStatement(RobotInfo robot, RobotTaskTemplate template) {
        long adId = userUtil.getId();
        TaskStatementAddDTO dto = new TaskStatementAddDTO()
                .setAdId(adId).setRobotIp(robot.getRobotIp())
                .setRobotNumber(robot.getRobotNumber())
                .setTaskTemplateId(Long.parseLong(template.getId()))
                .setTaskTemplateName(template.getName());
        List<String> list = JSON.parseArray(template.getTaskConfigSnapshot(), String.class);
        for (String task : list) {
            JSONObject object = JSON.parseObject(task);
            dto.setTaskId(object.getLong("taskId"));
            dto.setTaskName(object.getString("taskName"));
            Long statementId = object.getLong("statementId");
            if (Objects.isNull(statementId)) {
                log.warn("发布任务时创建任务报表失败没有设定报表id ! 管理员ID : {} 任务ID : {} 机器编号: {}"
                        , adId, dto.getTaskId(), dto.getRobotNumber());
                return;
            }
            dto.setStatementId(statementId);
            if (!robotTaskStatementService.add(dto)) {
                log.warn("发布任务时创建任务报表失败 ! 管理员ID : {} 任务ID : {} 机器编号: {}"
                        , adId, dto.getTaskId(), dto.getRobotNumber());
            }
        }
    }

    public RobotInfo checkRobotExist(Long robotNumber) {
        RobotInfo entity = getById(robotNumber);
        if (entity == null) {
            throw new ServiceException("该机器信息丟失 : " + request.getRemoteAddr() + " 编号 ： " + robotNumber);
        }
        return entity;
    }
}




